# -*- coding: utf-8 -*-
"""
标定命令执行器（HandlerCore）

功能目标：
- 根据 `calibration_type` 与 `calibration_key`（single/left/right）映射到具体的标定命令；
- 在标定动作窗口（CalibrationActionWindow）打开后执行阻塞式命令；
- 通过 `ClientCarThread.execute_long_command` 执行命令，并使用 callback 将终端输出实时显示到窗口的 `log_view`；
- 命令执行完成后自动退出执行线程（QThread），并清理资源。

实现约束：
- 使用Python 3.10，遵循PEP 8；
- 使用中文注释，代码简洁易懂；
- 充分考虑异常处理与连接状态检查；
- 不改变原有类的设计，仅在窗口创建完成后由外部调用启动执行。
"""

from __future__ import annotations

from typing import Optional, Callable, Dict

from PySide2.QtCore import QObject, QThread, QTimer, Signal

from gglobal import logger
from mooetoolapp.handlercore.language_manager import LanguageManager


class CalibrationCommandWorker(QThread):
    """标定命令工作线程

    负责在独立线程中调用 `ClientCarThread.execute_long_command`，
    并通过回调将输出追加到窗口日志。
    """

    # 输出信号（跨线程传递命令行输出到窗口）
    output_signal = Signal(str)

    def __init__(
        self,
        client_thread,
        command: str,
        timeout: int = 600,
        parent: Optional[QObject] = None,
    ) -> None:
        super().__init__(parent)
        self._client_thread = client_thread
        self._command = command
        self._timeout = timeout
        self._language = LanguageManager.get_instance()

    def run(self) -> None:
        """在线程中执行长时间命令"""
        try:
            # 线程开始提示（追加到日志）
            # 线程开始提示（通过信号传递）
            try:
                self.output_signal.emit(f"开始执行命令: {self._command}\n")
            except Exception:
                pass

            if not getattr(self._client_thread, "_connection_manager", None) or not self._client_thread._connection_manager.is_connected:
                msg = self._language.get_constant('vehicle_not_connected', '车辆未连接，无法执行命令')+"\n"
                logger.warning(msg)
                try:
                    self.output_signal.emit(msg)
                except Exception:
                    pass
                return

            # 进入 Docker 容器
            try:
                self.output_signal.emit("正在进入容器 mooe-core ...\n")
            except Exception:
                pass
            entered = False
            try:
                entered = self._client_thread.enter_docker_container("mooe-core")
            except Exception as e:
                logger.exception(f"进入容器异常: {e}")
                entered = False
            if not entered:
                msg = self._language.get_constant('enter_container_failed', '进入容器失败')+"\n"
                try:
                    self.output_signal.emit(msg)
                except Exception:
                    pass
                return

            # 执行阻塞式命令（底层按块输出通过callback回传）
            # 在容器中切换到指定路径再执行命令
            target_dir = "/home/muyi/mooe-core/share/Sensor_Calibration/launch/"
            composed_cmd = f"cd {target_dir} && {self._command}"
            # 通过回调捕获输出，当检测到字符串 "finish!" 时认为长命令正常完成
            finished_ok = False

            def _on_output(data):
                """输出回调：转发到窗口，并检测完成标识"""
                # 中文注释：标准化为字符串，便于检测
                text = str(data)
                # 转发到窗口日志
                try:
                    self.output_signal.emit(text)
                except Exception:
                    pass
                # 中文注释：检测程序输出的完成标识
                nonlocal finished_ok
                if "finish!" in text.lower():
                    finished_ok = True

            ok = self._client_thread.execute_long_command(
                composed_cmd,
                callback=_on_output,
                timeout=self._timeout,
            )

            # 结束提示
            # 中文注释：优先以输出中出现 "finish!" 作为成功完成的判定；
            # 若未检测到但底层返回 ok，也视为成功。
            if finished_ok or ok:
                end_msg = self._language.get_constant('command_completed', '命令执行完成')+"\n"
                logger.info(end_msg)
                try:
                    self.output_signal.emit(end_msg)
                except Exception:
                    pass
            else:
                err_msg = self._language.get_constant('long_command_failed', '长时间命令执行失败')+"\n"
                logger.error(err_msg)
                try:
                    self.output_signal.emit(err_msg)
                except Exception:
                    pass
        except Exception as exc:
            # 异常处理：写入日志与窗口
            err = f"执行命令异常: {exc}"
            logger.exception(err)
            try:
                self.output_signal.emit(err)
            except Exception:
                pass
        finally:
            # 线程运行结束后会自动发出 finished 信号
            # 资源由外部负责清理（deleteLater）
            # 无论正常结束或异常，尝试退出容器
            try:
                self._client_thread.exit_docker_container()
            except Exception:
                pass


class CalibrationCommandExecutor(QObject):
    """标定命令执行器

    外部使用方式：
    - 在 `CalibrationActionWindow` 创建并显示后，调用 `start_on_window_open(...)`；
    - 传入窗口实例、标定类型与键、以及已连接的 `ClientCarThread` 实例；
    - 执行器会在UI线程空闲时启动工作线程执行命令，并实时将输出写入窗口日志。
    """

    # 标定命令映射：calibration_type -> {mode -> command}
    COMMAND_MAP: Dict[str, Dict[str, str]] = {
        # 激光yaw标定（single）
        "calibration_menu_laser_yaw": {
            "single": "./Sensor_Calibration.sh up_lidar_wheel 1",
        },
        # 激光joint标定（single）
        "calibration_menu_laser_joint": {
            "single": "./Sensor_Calibration.sh up_lidar_down 1",
        },
        # Mid360标定（single）
        "calibration_menu_mid360": {
            "single": "./Sensor_Calibration.sh livox-rslidar 1",
        },
        # 叉车相机标定（left/right）
        "calibration_menu_fork_camera": {
            "left": "./Sensor_Calibration.sh arms_sensor_left 1",
            "right": "./Sensor_Calibration.sh arms_sensor_right 1",
        },
        # 叉车相机标定子项（left/right）
        "calibration_menu_fork_camera_sub1": {
            "left": "./Sensor_Calibration.sh arms_sensor_left 1",
            "right": "./Sensor_Calibration.sh arms_sensor_right 1",
        },
        # 识别相机1标定（single）
        "calibration_menu_recognition_camera_sub1": {
            "single": "./Sensor_Calibration.sh livox_arm_center 1",
        },
        # 识别相机2标定（left/right）
        "calibration_menu_recognition_camera_sub2": {
            "left": "./Sensor_Calibration.sh arms_sensor_left_fetch 1",
            "right": "./Sensor_Calibration.sh arms_sensor_right_fetch 1",
        },
    }

    # 执行器停止信号：当内部工作线程结束时发出
    worker_stopped = Signal()

    def __init__(self, parent: Optional[QObject] = None) -> None:
        super().__init__(parent)
        self._worker: Optional[CalibrationCommandWorker] = None
        self._language = LanguageManager.get_instance()
        # 停止流程状态与防重入标志（避免重复退出容器/重复发送中断）
        self._stop_initiated: bool = False  # 是否已经发起过停止流程
        self._stop_force_requested: bool = False  # 是否曾请求过强制停止
        self._exit_performed: bool = False  # 是否已执行过退出容器（确保只执行一次）

    def start_on_window_open(
        self,
        calibrationwindow,
        calibration_type: str,
        calibration_key: str,
        client_thread,
        timeout: int = 600,
    ) -> None:
        """窗口打开后启动命令执行

        Args:
            calibrationwindow: CalibrationActionWindow 实例（用于日志输出）
            calibration_type: 标定类型键
            calibration_key: 标定模式键（single/left/right）
            client_thread: ClientCarThread 实例（需处于连接状态）
            timeout: 命令执行超时（秒）
        """

        # 通过定时器在UI空闲时启动，确保窗口已显示
        QTimer.singleShot(0, lambda: self._start_internal(calibrationwindow, calibration_type, calibration_key, client_thread, timeout))

    def _start_internal(
        self,
        calibrationwindow,
        calibration_type: str,
        calibration_key: str,
        client_thread,
        timeout: int,
    ) -> None:
        """内部启动逻辑：解析命令并启动工作线程"""
        try:
            cmd = self._resolve_command(calibration_type, calibration_key)
            if not cmd:
                msg = f"未匹配到标定命令: type={calibration_type}, key={calibration_key}"
                logger.warning(msg)
                try:
                    calibrationwindow.append_log_safe(msg)
                except Exception:
                    pass
                return

            # 输出回调：写入窗口日志（线程安全方法）
            # 启动工作线程
            self._worker = CalibrationCommandWorker(
                client_thread=client_thread,
                command=cmd,
                timeout=timeout,
                parent=self,
            )
            # 连接输出信号到窗口日志（通过窗口内部的线程安全追加方法）
            try:
                self._worker.output_signal.connect(calibrationwindow.append_log_safe)
            except Exception:
                # 若连接失败则忽略（可能窗口已关闭）
                pass
            # 执行完毕自动清理
            self._worker.finished.connect(self._on_worker_finished)
            self._worker.start()
        except Exception as exc:
            err = f"启动标定命令失败: {exc}"
            logger.exception(err)
            try:
                calibrationwindow.append_log_safe(err)
            except Exception:
                pass

    def _on_worker_finished(self) -> None:
        """工作线程结束后清理资源"""
        try:
            # 使用 sender() 获取实际结束的线程对象，避免 _worker 引用已被置空时无法清理
            try:
                finished_thread = self.sender()
            except Exception:
                finished_thread = None
            if finished_thread:
                try:
                    finished_thread.deleteLater()
                except Exception:
                    pass
            # 兼容旧逻辑：若仍保留引用，则也尝试清理
            if self._worker:
                try:
                    self._worker.deleteLater()
                except Exception:
                    pass
        except Exception:
            pass
        finally:
            self._worker = None
            # 重置停止流程标志，便于下次正常运行
            self._stop_initiated = False
            self._stop_force_requested = False
            self._exit_performed = False
            # 通知外部：工作线程已停止
            try:
                self.worker_stopped.emit()
            except Exception:
                pass

    def stop(self, force: bool = False) -> None:
        """停止当前执行的标定命令，避免窗口关闭时线程仍在运行。

        参数：
        - force: 是否在正常停止失败时强制终止线程（不推荐，但可避免崩溃）。
        """
        try:
            worker = self._worker
            if not worker:
                return
            # 防重入：若已发起停止且本次不是升级为强制停止，则直接返回
            if self._stop_initiated and not force:
                return
            # 标记已发起停止流程；若是强制停止则提升标记
            self._stop_initiated = True
            if force:
                self._stop_force_requested = True
            # 尝试优雅中断：先发送 Ctrl+C，再退出容器
            client = getattr(worker, "_client_thread", None)
            if client:
                try:
                    # 先向当前会话发送 Ctrl+C，优雅通知子进程终止
                    try:
                        client.send_ctrl_c()
                        client.send_ctrl_c()
                    except Exception:
                        pass
                except Exception:
                    pass
            # 非阻塞停止：避免在主线程中调用等待造成UI卡顿
            # 如仍在运行且需要强制终止，仅调用 terminate()；退出容器仅执行一次
            if force and worker.isRunning():
                try:
                    # 若尚未执行过退出容器，则执行一次退出容器（避免重复调用导致异常）
                    if client and not self._exit_performed:
                        try:
                            if getattr(client, "in_docker_container", False):
                                client.exit_docker_container()
                        except Exception:
                            pass
                        finally:
                            self._exit_performed = True
                except Exception:
                    pass
                try:
                    worker.terminate()
                except Exception:
                    pass

            # 注意：不在此处调用 deleteLater()，由 _on_worker_finished 统一清理，
            # 以避免线程仍在运行时触发QThread析构导致崩溃。
            if not worker.isRunning():
                # 线程已停止时，主动将引用置空，等待 _on_worker_finished 进一步清理
                self._worker = None
        except Exception as exc:
            logger.exception(f"停止标定命令线程失败: {exc}")

    def is_running(self) -> bool:
        """查询当前是否仍有工作线程在运行"""
        try:
            return bool(self._worker and self._worker.isRunning())
        except Exception:
            return False

    def _resolve_command(self, calibration_type: str, calibration_key: str) -> Optional[str]:
        """解析标定命令

        支持按 `single/left/right` 模式取值；若当前类型缺少对应模式，则回退到 `single`（如果存在）。
        """
        try:
            type_map = self.COMMAND_MAP.get(calibration_type, {})
            if not type_map:
                return None
            # 优先使用指定模式
            if calibration_key in type_map:
                return type_map[calibration_key]
            # 回退使用 single（若存在）
            return type_map.get("single")
        except Exception:
            return None
